package com.hwd.gmall.seckill.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hwd.gmall.common.constant.SeckillConst;
import com.hwd.gmall.common.enums.SeckillOrderStatusEnum;
import com.hwd.gmall.common.enums.SeckillStatusEnum;
import com.hwd.gmall.common.util.MyThreadLocal;
import com.hwd.gmall.model.activity.SeckillGoods;
import com.hwd.gmall.seckill.mapper.SeckillOrderMapper;
import com.hwd.gmall.seckill.pojo.SeckillOrder;
import com.hwd.gmall.seckill.pojo.UserRecode;
import com.hwd.gmall.seckill.service.SeckillOrderService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;

/**
 * 秒杀订单业务的接口实现类
 *
 * @author 黄伟东/Victor
 * @date 2022/5/11 19:28
 */
@Service
public class SeckillOrderServiceImpl implements SeckillOrderService {

    /** 用于判断秒杀订单数据存储的位置的标识，true:数据库，false:redis */
    Boolean flag = true;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private SeckillOrderMapper seckillOrderMapper;

    @Resource
    private RabbitTemplate rabbitTemplate;

    /**
     * 秒杀下单：排队
     * 保存到redis中?(数据主要用来查，数据库效率低，扛不住高并发)，
     * 用hash?(效率，弊端key丢失→不要紧，重新排队)
     *
     * @param startTime 指定时间段
     * @param goodsId   商品id
     * @param num       下单数量
     * @return com.hwd.gmall.seckill.pojo.UserRecode
     */
    @Override
    public UserRecode addSeckillOrder(String startTime, String goodsId, Integer num) {
        // 获取用户名
        String username = MyThreadLocal.get();

        // 包装排队对象
        UserRecode userRecode = new UserRecode();

        // 解决用户重复排队的问题
        Long increment = redisTemplate.opsForValue().increment(SeckillConst.USER_QUEUE_COUNT + username, 1);
        if (increment != null && increment > 1) {
            // 重复排队
            userRecode.setStatus(SeckillStatusEnum.THREE.getStatus());
            userRecode.setMsg("秒杀失败,重复排队！");
            return userRecode;
        }

        // 记录
        userRecode.setUsername(username);
        userRecode.setCreateTime(new Date());
        userRecode.setStatus(SeckillStatusEnum.ONE.getStatus());
        userRecode.setGoodsId(goodsId);
        userRecode.setTime(startTime);
        userRecode.setNum(num);
        userRecode.setMsg("排队成功");

        // 包排队对象保存到redis中
        redisTemplate.opsForHash().put(SeckillConst.USER_RECODE_INFO, username, userRecode);

        // 异步下单
        rabbitTemplate.convertAndSend(
                SeckillConst.SECKILL_ORDER_EXCHANGE,
                SeckillConst.SECKILL_ORDER_ROUTING_KEY,
                JSONObject.toJSONString(userRecode));
        // 返回排队的结果
        return userRecode;
    }

    /**
     * 查询用户的排队状态，即下单状态
     *
     * @return com.hwd.gmall.seckill.pojo.UserRecode
     */
    @Override
    public UserRecode getUserRecode() {
        // 获取用户名
        String username = MyThreadLocal.get();
        return (UserRecode) redisTemplate.opsForHash().get("User_Recode_Info", username);
    }

    /**
     * 取消秒杀订单
     *
     * @param orderId  秒杀订单号
     * @param username 用户名
     */
    @Override
    public void cancelSeckillOrder(String orderId, String username) {

        // 从数据库获取秒杀订单的数据
        SeckillOrder seckillOrder = getSeckillOrderFromDbOrRedis(orderId);

        if (seckillOrder == null) {
            return;
        }

        // 去到秒杀订单数据，前置状态判断
        if (SeckillOrderStatusEnum.ZERO.getStatus().equals(seckillOrder.getStatus())) {
            // 订单存在，修改订单的状态
            // 用户名存在,是主动取消,用户名不存在是超时取消
            seckillOrder.setStatus(username == null ?
                    SeckillOrderStatusEnum.FOUR.getStatus() : SeckillOrderStatusEnum.THREE.getStatus());
            // 判断订单存储的位置
            if (flag) {
                // 在数据库中
                int i;
                if (StringUtils.isEmpty(username)) {
                    // 超时
                    i = seckillOrderMapper.updateById(seckillOrder);
                } else {
                    // 主动
                    i = seckillOrderMapper.update(seckillOrder,
                            new LambdaQueryWrapper<SeckillOrder>()
                                    .eq(SeckillOrder::getId, orderId).eq(SeckillOrder::getUserId, username));
                }
                if (i < 0) {
                    throw new RuntimeException("保存数据到数据库失败");
                }
            } else {
                // 数据库没有数据,做insert
                int insert = seckillOrderMapper.insert(seckillOrder);
                if (insert <= 0) {
                    throw new RuntimeException("保存数据到数据库失败");
                }
            }
        }

        // 后续操作：回滚库存
        rollbackSeckillGoodsStock(seckillOrder.getGoodsId(), seckillOrder.getNum(), seckillOrder.getUserId());

        // 后续操作：清除标识位
        clearSeckillFlag(username, seckillOrder.getId());
    }

    /**
     * 修改秒杀订单
     *
     * @param msg 信息
     */
    @Override
    public void updateSeckillOrder(String msg) {
        // 将通知的结果反序列化
        Map<String, String> map = JSONObject.parseObject(msg, Map.class);

        // 获取订单号
        String orderId = map.get("out_trade_no");

        // 从数据库获取秒杀订单的数据
        SeckillOrder seckillOrder = getSeckillOrderFromDbOrRedis(orderId);

        if (seckillOrder == null) {
            return;
        }

        // 幂等性
        if (!SeckillOrderStatusEnum.ZERO.getStatus().equals(seckillOrder.getStatus())) {
            return;
        }

        //修改订单的状态为已支付
        seckillOrder.setStatus(SeckillOrderStatusEnum.ONE.getStatus());
        int i;
        //判断订单存储的位置
        if (flag) {
            //数据库有直接改数据库的数据
            i = seckillOrderMapper.updateById(seckillOrder);
        } else {
            //若数据库没有数据,做insert
            i = seckillOrderMapper.insert(seckillOrder);
        }

        if (i < 0) {
            throw new RuntimeException("修改秒杀订单的支付结果失败");
        }

        // 后续处理: 清理标识位
        clearSeckillFlag(seckillOrder.getUserId(), orderId);
    }

    /**
     * 根据订单id在数据库和redis中查询秒杀订单的信息
     *
     * @param orderId 订单id
     * @return com.hwd.gmall.seckill.pojo.SeckillOrder
     */
    private SeckillOrder getSeckillOrderFromDbOrRedis(String orderId) {
        // 从数据库中获取秒杀订单的数据
        SeckillOrder seckillOrder = seckillOrderMapper.selectById(orderId);

        // 判断数据库中有没有数据
        if (seckillOrder == null || seckillOrder.getId() == null) {
            // 数据库中没有数据，从redis中取
            seckillOrder = (SeckillOrder) redisTemplate.opsForHash().get(SeckillConst.SECKILL_USER_ORDER, orderId);
            if (seckillOrder == null || seckillOrder.getId() == null) {
                return null;
            }
            // 表示秒杀订单的数据
            flag = false;
        }

        return seckillOrder;
    }

    /**
     * 清理标识位不影响用户下次下单
     *
     * @param username 用户名
     * @param orderId  订单id
     */
    private void clearSeckillFlag(String username, String orderId) {
        // 清理用户的排队计数器
        redisTemplate.delete(SeckillConst.USER_QUEUE_COUNT + username);
        // 清理用户的排队信息
        redisTemplate.delete(SeckillConst.USER_RECODE_INFO + username);
        // 清理用户的订单数据
        redisTemplate.opsForHash().delete(SeckillConst.SECKILL_USER_ORDER, orderId);
    }

    /**
     * 回滚redis中的商品的库存
     *
     * @param goodsId  商品id
     * @param num      回滚的数量
     * @param username 用户名
     */
    private void rollbackSeckillGoodsStock(String goodsId, Integer num, String username) {

        // 获取排队的状态信息
        UserRecode userRecode =
                (UserRecode) redisTemplate.opsForHash().get(SeckillConst.USER_RECODE_INFO, username);
        if (userRecode == null) {
            return;
        }
        // 回滚商品的库存自增值（同步商品剩余库存到数据库的依据）：一定要回滚
        Long increment =
                redisTemplate.opsForHash().increment(SeckillConst.SECKILL_GOODS_STOCK + userRecode.getTime(), goodsId, num);

        // 判断商品活动是否结束
        if (Boolean.TRUE.equals(redisTemplate.hasKey(SeckillConst.SECKILL_GOODS_STOCK_COUNT_QUEUE + goodsId))) {

            // 商品的队列（下单依据）：活动结束不需要回滚
            redisTemplate.opsForList().leftPushAll(
                    SeckillConst.SECKILL_GOODS_STOCK_COUNT_QUEUE + goodsId, getGoodsIds(goodsId, num));

            // 商品数据更新
            SeckillGoods seckillGoods =
                    (SeckillGoods) redisTemplate.opsForHash().get(userRecode.getTime(), goodsId);
            if (seckillGoods != null) {
                // 设置回滚后的商品的库存数据
                seckillGoods.setStockCount(increment.intValue());
                redisTemplate.opsForHash().put(userRecode.getTime(), goodsId, seckillGoods);
            }
        }

    }

    /**
     * 给指定的商品创建一个数组，长度为商品库存长度，元素全部是当前商品id
     *
     * @param goodsId    商品id
     * @param stockCount 商品库存
     * @return java.lang.String[]
     */
    private String[] getGoodsIds(String goodsId, Integer stockCount) {
        String[] ids = new String[stockCount];
        for (int i = 0; i < stockCount; i++) {
            ids[i] = goodsId;
        }
        return ids;
    }
}
